package com.trg.flex.search
{
	import com.trg.flex.MetaDataUtils;
	
	import flash.events.EventDispatcher;
	
	import mx.collections.Sort;
	import mx.collections.SortField;
	
	/**
	 * <p>This is basically a mirror of the Search object in the Java side of the framework.
	 * It has a few differences like the addition of the searchVO and sort properties.
	 * (See <code>Search</code> in the documentation for Generic DAO.)
	 * 
	 * <p>It maps to <code>com.trg.remote.RemoteSearch</code> on the server side, which is
	 * a subclass of <code>Search</code> that exposes the properties for Blaze DS to use in
	 * serialization.
	 */
	[RemoteClass(alias="com.trg.search.flex.FlexSearch")]
	[Bindable]
	public class Search extends EventDispatcher
	{
		public static const RESULT_AUTO : int = 0;
		public static const RESULT_ARRAY : int = 1;
		public static const RESULT_LIST : int = 2;
		public static const RESULT_MAP : int = 3;
		public static const RESULT_SINGLE : int = 4;
		
		
		public function Search(type:* = null)
		{
			if (type is String) {
				this.searchClassName = type;
			} else if (type is Class) {
				this.searchVO = type;
			}
		}
		
		/**
		 * <p>The name of the server-side class to search for. <code>searchVO</code> is an
		 * alternative way to set this property.
		 * 
		 * <p>This maps to <code>Search.searchClass</code> on the server side.
		 * 
		 * @see #searchVO
		 */
		public var searchClassName : String;
		
		//Paging
		public var firstResult : int;
		public var page : int;
		public var maxResults : int;
		
		//Filters
		[ArrayElementType("com.trg.flex.search.Filter")]
		public var filters : Array = [];
		public var disjunction : Boolean;
		
		//Sorting
		/**
		 * This is a Flex <code>Sort</code> object which is mapped
		 * by this class to <code>Search.sorts</code> on the server side.
		 */
		public var sort : Sort;
		
		//Field
		[ArrayElementType("com.trg.flex.search.Field")]
		public var fields : Array = [];
		public var resultMode : int = RESULT_AUTO;
		
		//Fetch
		[ArrayElementType("String")]
		public var fetches : Array = [];
		
		
		/**
		 * Add either a new <code>Filter</code> or a new <code>Fetch</code> to the search.
		 * 
		 * @param object - must be a <code>Filter</code> or <code>Fetch</code>.
		 */
		public function add(object:*):Search {
			if (object == null)
				return this;
			if (object is Filter)
				filters.push(object);
			else if (object is Field)
				fields.push(object);
			else if (object is String)
				fetches.push(object);
			else
				throw (new Error("Invalid parameter to Search.add(). " + object.toString() + " is not a Filter or Fetch."));
			return this;
		}
		
		//ClassName Methods
		public function set searchVO(value:Class):void {
			if (value == null) searchClassName = null;
			searchClassName = MetaDataUtils.getRemoteClass(value);
		}
		
		/**
		 * <p>The value object to search for. This is an alternative way of setting className.
		 * 
		 * <p>This maps to <code>Search.searchClass</code> on the server side.
		 * 
		 * @see #searchClass
		 */
		public function get searchVO():Class {
			if (searchClassName == null) return null;
			return MetaDataUtils.getTypeFromRemoteClass(searchClassName);
		}
		
		//Filter Methods
		public function addFilter(property:String, value:Object, operator:int):Search {
			filters.push(new Filter(property, value, operator));
			return this;
		}
		public function addFilterEquals(property:String, value:Object):Search {
			addFilter(property, value, Filter.OP_EQUAL);
			return this;
		}
		
		//Remote Interface
		/**
		 * This getter and setter map the server-side <code>Search.sorts</code> to
		 * the Flex Sort object of the <code>sort</code> property.
		 * 
		 * @see #sort
		 */
		public function get sorts():Array {
			if (sort == null || sort.fields == null || sort.fields.length == 0) return null;
			var result:Array = [];
			for each (var field:SortField in sort.fields) {
				result.push(new RemoteSort(field.name, field.descending));
			}
			return result;
		}
		public function set sorts(value:Array):void {
			if (value == null || value.length == 0) {
				sort = null;
			} else {
				var fields:Array = [];
				for each (var field:* in value) {
					fields.push(new SortField(field.property, false, field.desc));
				}
				var s:Sort = new Sort();
				s.fields = fields;
				sort = s;
			}
		}
		
		//Local Filtering
		/**
		 * Set this function as the filterFunction of a collection to filter
		 * the collection according to the filters specified in the <code>Search</code>.
		 */
		public function filterFunction(o:*):Boolean {
			if (filters == null || filters.length == 0) return true;
			
			var filter:Filter;
			if (disjunction) {
				for each (filter in filters) {
					if (filter != null && filter.doFilter(o)) return true;
				}
				return false;
			} else {
				for each (filter in filters) {
					if (filter != null && !filter.doFilter(o)) return false;
				}
				return true;
			}
		}
		
	}
}